﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Platform.Storage.Entity;
using Platform.ObjectModel;

namespace Platform.Storage.Impl
{
	/// <summary>
	/// Auxilia na criação de expressões 
	/// </summary>
	internal static class TranslatorHelper
	{

		/// <summary>
		/// 
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		public static bool HasCriteria( this MethodCallExpression node )
		{
			string methodName = node.Method.Name;
			return node.Arguments.Count == 2
				&& ( methodName == "Where"
				|| methodName == "FirstOrDefault"
				|| methodName == "First" );
		}

		/// <summary>
		/// Cria a expressão que realiza a chamada para o método <paramref name="methodName"/>
		/// da class <see cref="Queryable"/>.
		/// </summary>
		/// <param name="methodName"></param>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public static MethodCallExpression MakeQueryableCall( string methodName, params Expression[] parameters )
		{
			return MakeQueryableCall( typeof( FieldValue ), methodName, parameters );
		}

		public static MethodCallExpression MakeQueryableCall( Type entityType, string methodName, params Expression[] parameters )
		{
			var genericMethod = typeof( Queryable )
									.GetMethods()
									.First(
										t => t.Name == methodName
											&& t.GetParameters().Length == parameters.Length );
			var methodInfo = genericMethod.MakeGenericMethod( entityType );

			return Expression.Call( methodInfo, parameters );
		}

		internal static MethodCallExpression Where( Expression query, Expression<Func<FieldValue, bool>> condition )
		{
			return TranslatorHelper.MakeQueryableCall( "Where", query, condition );
		}

		/// <summary>
		/// Verifica se o tipo <paramref name="objType"/> é um documento
		/// </summary>
		/// <param name="objType"></param>
		/// <returns></returns>
		internal static bool IsDocument( this Type objType )
		{
			Type docType = typeof( DocumentFlake );
			return objType.IsSubclassOf( docType ) || objType.Equals( docType );
		}


		internal static Expression Select( Expression query, string fieldName )
		{
			Expression<Func<FieldValue, bool>> fieldSelector = ( f => f.LoweredFieldName == fieldName );
			return MakeQueryableCall(
				"Where",
				query,
				fieldSelector );
		}


		/// <summary>
		/// Realiza a conversão para fazer a pesquisa por uma propriedade da entidade
		/// </summary>
		/// <param name="fields"></param>
		/// <param name="fieldName"></param>
		/// <param name="fieldValue"></param>
		/// <param name="entityName"></param>
		/// <returns></returns>
		internal static Expression<Func<FieldValue, bool>> PropertyEquality( IQueryable<FieldValue> fields, string fieldName, Expression fieldValue, string entityName )
		{
			string lowredName = fieldName.ToLowerInvariant();
			string marker = String.Concat( lowredName, "marker" );

			Expression<Func<FieldValue, bool>> result =
				( entityField ) => fields.Any(
					( field ) => entityField.ReversedTokenId == field.ReversedTokenId
							//&& field.LoweredFieldName.StartsWith( entityName )
							&& field.LoweredFieldName == lowredName
							&& field.TextValue == marker );

			return result
				.Replace<MemberExpression>( m => m.Member.Name == "marker", fieldValue )
				.CastToLambda();
		}

		internal static Expression<Func<FieldValue, bool>> CastToLambda( this Expression node )
		{
			return ( Expression<Func<FieldValue, bool>> )node;
		}

		/// <summary>
		/// Realiza a concatenação de varias expressões
		/// </summary>
		/// <param name="left"></param>
		/// <param name="right"></param>
		/// <param name="more"></param>
		/// <returns></returns>
		internal static BinaryExpression And( Expression left, Expression right, params Expression[] more )
		{
			BinaryExpression result = Expression.AndAlso( left, right );
			if ( more != null )
				for ( int index = 0; index < more.Length; index++ )
					result = And( result, more[ index ] );
			return result;
		}

		/// <summary>
		/// Cria a expressão que verifica se o campo <see cref="FieldValue.LoweredFieldName"/> começa com o
		/// conteúdo do parâmetro <paramref name="name"/>.
		/// </summary>
		/// <param name="fields"></param>
		/// <param name="name"></param>
		/// <returns></returns>
		internal static Expression<Func<IQueryable<FieldValue>>> StartingWith( this IQueryable<FieldValue> fields, string name )
		{
			return () => fields.Where( StartingWith( name ) );
		}

		/// <summary>
		/// Cria a expressão que verifica se o campo <see cref="FieldValue.LoweredFieldName"/> começa com o
		/// conteúdo do parâmetro <paramref name="name"/>.
		/// </summary>
		/// <param name="fields"></param>
		/// <param name="name"></param>
		/// <returns></returns>
		internal static Expression<Func<FieldValue, bool>> StartingWith( string name )
		{
			return f => f.LoweredFieldName.StartsWith( name );
		}

		/// <summary>
		/// Verifica se o mesmo campo está sendo utilizado várias vezes na pesquisa
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		internal static bool UsingSameFields( this BinaryExpression node )
		{
			var fields = TranslatorHelper.ExtractFields( node );
			return fields.Count == 1;
		}

		/// <summary>
		/// Verifica quais campos são utilizados no critério
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		internal static List<string> ExtractFields( this BinaryExpression node )
		{
			var extractor = new DataObjectFieldExtractor();
			return extractor.Extract( node );
		}
	}


	internal sealed class DataObjectFieldExtractor : ExpressionVisitor
	{

		public DataObjectFieldExtractor()
		{ }


		public List<string> Fields
		{ get; private set; }

		protected override Expression VisitMember( MemberExpression node )
		{
			string propertyName = node.GetPropertyName();
			if ( String.IsNullOrEmpty( propertyName ) )
			{
				return base.VisitMember( node );
			}
			else
			{
				if ( !this.Fields.Contains( propertyName ) )
				{
					this.Fields.Add( propertyName );
				}
				return node;
			}
		}

		public List<string> Extract( Expression node )
		{
			this.Fields = new List<string>();
			base.Visit( node );
			return this.Fields;
		}
	}
}
